Coverage Report

Created: 2024-12-19 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\tools.proto\tools.proto\compiler\src\gen\template\core.rs
Line
Count
Source
1
// Copyright (c) 2024, BlockProject 3D
2
//
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without modification,
6
// are permitted provided that the following conditions are met:
7
//
8
//     * Redistributions of source code must retain the above copyright notice,
9
//       this list of conditions and the following disclaimer.
10
//     * Redistributions in binary form must reproduce the above copyright notice,
11
//       this list of conditions and the following disclaimer in the documentation
12
//       and/or other materials provided with the distribution.
13
//     * Neither the name of BlockProject 3D nor the names of its contributors
14
//       may be used to endorse or promote products derived from this software
15
//       without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
use crate::gen::template::options::Options;
30
use crate::gen::template::parse_tree::{Component, Fragment, FragmentMode, Token};
31
use crate::gen::template::Error;
32
use itertools::Itertools;
33
use std::borrow::Cow;
34
use std::collections::HashMap;
35
use crate::gen::codec::CodecMap;
36
37
pub struct Template<'fragment, 'variable> {
38
    fragments: HashMap<String, Fragment<'fragment>>,
39
    variables: HashMap<&'variable str, Cow<'variable, str>>,
40
}
41
42
impl<'fragment, 'variable> Template<'fragment, 'variable> {
43
130
    pub fn compile(data: &'fragment [u8]) -> Result<Self, Error> {
44
130
        Self::compile_with_options(data, &Options::default())
45
130
    }
46
47
372
    pub fn compile_with_options(data: &'fragment [u8], options: &Options) -> Result<Self, Error> {
48
372
        Self::compile_with_options_includes(data, &CodecMap::default(), options)
49
372
    }
50
51
6
    pub fn compile_with_includes(data: &'fragment [u8], codecs: &CodecMap<'fragment, '_>) -> Result<Self, Error> {
52
6
        Self::compile_with_options_includes(data, codecs, &Options::default())
53
6
    }
54
55
378
    pub fn compile_with_options_includes(data: &'fragment [u8], codecs: &CodecMap<'fragment, '_>, options: &Options) -> Result<Self, Error> {
56
378
        let mut fragments = HashMap::new();
57
378
        let mut frag_stack = Vec::new();
58
1.01M
        let lines = data.split(|v| *v == b'\n'
)378
;
59
32.1k
        for 
line31.7k
in lines {
60
31.7k
            if line.is_empty() {
  Branch (60:16): [True: 259, False: 31.3k]
  Branch (60:16): [True: 1, False: 176]
61
260
                continue;
62
31.5k
            }
63
31.5k
            let line = if line[line.len() - 1] == b'\r' {
  Branch (63:27): [True: 31.2k, False: 118]
  Branch (63:27): [True: 176, False: 0]
64
31.4k
                &line[..line.len() - 1]
65
            } else {
66
118
                line
67
            };
68
31.5k
            if line.is_empty() {
  Branch (68:16): [True: 1.82k, False: 29.5k]
  Branch (68:16): [True: 19, False: 157]
69
1.84k
                continue;
70
29.6k
            }
71
29.6k
            if line.starts_with(b"#include ") && 
frag_stack.is_empty()6
{
  Branch (71:16): [True: 6, False: 29.5k]
  Branch (71:50): [True: 6, False: 0]
  Branch (71:16): [True: 0, False: 157]
  Branch (71:50): [True: 0, False: 0]
72
6
                let name = std::str::from_utf8(&line[9..]).map_err(|_| 
Error::InvalidUTF80
)
?0
;
73
6
                let template = codecs.get(name).ok_or_else(|| 
Error::IncludeNotFound(name.into())0
)
?0
;
74
36
                
fragments.extend(template.fragments.iter().map(6
|(k, v)| (k.clone(), v.clone())));
75
29.6k
            } else if line.starts_with(b"#fragment push ") {
  Branch (75:23): [True: 4.06k, False: 25.4k]
  Branch (75:23): [True: 8, False: 149]
76
4.07k
                let fragment = std::str::from_utf8(&line[15..]).map_err(|_| 
Error::InvalidUTF80
)
?0
;
77
4.07k
                if let Some(
id129
) = fragment.find(":") {
  Branch (77:24): [True: 129, False: 3.93k]
  Branch (77:24): [True: 0, False: 8]
78
129
                    let name = &fragment[..id];
79
129
                    let mode = &fragment[id + 1..];
80
129
                    frag_stack.push(Fragment {
81
129
                        name,
82
129
                        content: Vec::new(),
83
129
                        mode: FragmentMode::from_str(mode).ok_or_else(|| 
Error::UnknownFragmentMode(mode.into())0
)
?0
,
84
                    });
85
3.94k
                } else {
86
3.94k
                    frag_stack.push(Fragment {
87
3.94k
                        name: fragment,
88
3.94k
                        content: Vec::new(),
89
3.94k
                        mode: FragmentMode::Default,
90
3.94k
                    });
91
3.94k
                }
92
25.6k
            } else if line.starts_with(b"#fragment pop") {
  Branch (92:23): [True: 4.06k, False: 21.3k]
  Branch (92:23): [True: 8, False: 141]
93
4.07k
                if frag_stack.is_empty() {
  Branch (93:20): [True: 0, False: 4.06k]
  Branch (93:20): [True: 0, False: 8]
94
0
                    return Err(Error::InvalidPop);
95
4.07k
                }
96
7.69k
                let combined_name = frag_stack.iter().map(|v| v.name).join(".");
97
4.07k
                //SAFETY: this is fine because the fragment stack must not be empty at this point.
98
4.07k
                let mut fragment = unsafe { frag_stack.pop().unwrap_unchecked() };
99
4.07k
                if options.is_fragment_disabled(&combined_name) {
  Branch (99:20): [True: 4, False: 4.05k]
  Branch (99:20): [True: 0, False: 8]
100
4
                    fragment.content.clear();
101
4.06k
                }
102
4.07k
                fragments.insert(combined_name, fragment);
103
            } else {
104
21.5k
                let cur_fragment = frag_stack.last_mut().ok_or(Error::NoFragment)
?0
;
105
21.5k
                let mut token = Token::new(line);
106
817k
                while token.has_next() {
  Branch (106:23): [True: 791k, False: 21.3k]
  Branch (106:23): [True: 5.19k, False: 141]
107
796k
                    if token.cur() == b'{' {
  Branch (107:24): [True: 22.2k, False: 768k]
  Branch (107:24): [True: 90, False: 5.10k]
108
22.3k
                        if token.next() == Some(b'{') {
  Branch (108:28): [True: 5.52k, False: 16.7k]
  Branch (108:28): [True: 44, False: 46]
109
5.56k
                            token.inc();
110
16.7k
                        }
111
22.3k
                        if let Some(
component20.9k
) = token.pop()
?0
.map(Component::Constant) {
  Branch (111:32): [True: 20.8k, False: 1.35k]
  Branch (111:32): [True: 90, False: 0]
112
20.9k
                            cur_fragment.content.push(component);
113
20.9k
                        
}1.35k
114
773k
                    } else if token.cur() == b'}' {
  Branch (114:31): [True: 22.2k, False: 746k]
  Branch (114:31): [True: 90, False: 5.01k]
115
22.3k
                        if token.next() == Some(b'}') {
  Branch (115:28): [True: 5.52k, False: 16.7k]
  Branch (115:28): [True: 44, False: 46]
116
5.56k
                            token.inc();
117
5.56k
                            if let Some(component) = token.pop()
?0
.map(Component::Constant) {
  Branch (117:36): [True: 5.52k, False: 0]
  Branch (117:36): [True: 44, False: 0]
118
5.56k
                                cur_fragment.content.push(component);
119
5.56k
                            
}0
120
16.7k
                        } else if let Some(component) =
  Branch (120:39): [True: 16.7k, False: 0]
  Branch (120:39): [True: 46, False: 0]
121
16.7k
                            token.pop()
?0
.map(|v| Component::parse_variable(options.functions(), v))
122
                        {
123
16.7k
                            cur_fragment.content.push(component
?0
);
124
0
                        }
125
751k
                    }
126
796k
                    token.inc()
127
                }
128
21.5k
                if let Some(
component9.75k
) = token.pop()
?0
.map(Component::Constant) {
  Branch (128:24): [True: 9.69k, False: 11.7k]
  Branch (128:24): [True: 63, False: 78]
129
9.75k
                    cur_fragment.content.push(component);
130
11.7k
                }
131
21.5k
                cur_fragment.content.push(Component::NewLine);
132
            }
133
        }
134
378
        Ok(Template {
135
378
            fragments,
136
378
            variables: HashMap::new(),
137
378
        })
138
378
    }
139
140
1.13k
    pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self {
141
1.13k
        self.variables.insert(key, value.into());
142
1.13k
        self
143
1.13k
    }
144
145
65
    pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self {
146
65
        self.variables.insert(key, value.to_string().into());
147
65
        self
148
65
    }
149
150
2.37k
    fn render_internal(
151
2.37k
        &self,
152
2.37k
        variables: &HashMap<&str, Cow<str>>,
153
2.37k
        path: &str,
154
2.37k
        fragments: &[&str],
155
2.37k
    ) -> Result<String, Error> {
156
2.37k
        let mut rendered = Vec::new();
157
4.97k
        for 
name2.60k
in fragments {
158
2.60k
            let name: Cow<str> = match path.is_empty() {
159
1.68k
                false => Cow::Owned(format!("{}.{}", path, name)),
160
924
                true => Cow::Borrowed(name),
161
            };
162
2.60k
            let fragment = self.fragments.get(&*name).ok_or_else(|| 
Error::FragmentNotFound(String::from(&*name))0
)
?0
;
163
2.60k
            let sub_rendered = fragment
164
2.60k
                .content
165
2.60k
                .iter()
166
48.7k
                .map(|v| match v {
167
23.2k
                    Component::Constant(v) => Ok(Cow::Borrowed(*v)),
168
9.87k
                    Component::Variable(v) => {
169
9.87k
                        let variable = variables.get(v.name).ok_or_else(|| 
Error::VariableNotFound(v.name.into())0
)
?0
;
170
9.87k
                        if let Some(
function129
) = v.function {
  Branch (170:32): [True: 129, False: 9.74k]
  Branch (170:32): [True: 0, False: 4]
171
129
                            let variable = function(variable);
172
129
                            Ok(variable)
173
                        } else {
174
9.74k
                            Ok(Cow::Borrowed(&**variable))
175
                        }
176
                    }
177
15.6k
                    Component::NewLine => Ok(Cow::Borrowed("\n")),
178
48.7k
                })
179
2.60k
                .collect::<Result<Vec<Cow<str>>, Error>>()
?0
180
2.60k
                .join("");
181
2.60k
            if fragment.mode == FragmentMode::Inline {
  Branch (181:16): [True: 148, False: 2.45k]
  Branch (181:16): [True: 0, False: 1]
182
148
                rendered.push(sub_rendered.trim().into());
183
2.45k
            } else {
184
2.45k
                rendered.push(sub_rendered);
185
2.45k
            }
186
        }
187
2.37k
        Ok(rendered.join(""))
188
2.37k
    }
189
190
1.58k
    pub fn scope(&self) -> Scope {
191
1.58k
        Scope {
192
1.58k
            template: self,
193
1.58k
            variables: self.variables.clone(),
194
1.58k
        }
195
1.58k
    }
196
197
465
    pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> {
198
465
        self.render_internal(&self.variables, path, fragments)
199
465
    }
200
}
201
202
#[derive(Clone)]
203
pub struct Scope<'a, 'fragment, 'variable> {
204
    template: &'a Template<'fragment, 'variable>,
205
    variables: HashMap<&'variable str, Cow<'variable, str>>,
206
}
207
208
impl<'variable> Scope<'_, '_, 'variable> {
209
4.35k
    pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self {
210
4.35k
        self.variables.insert(key, value.into());
211
4.35k
        self
212
4.35k
    }
213
214
3.19k
    pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self {
215
3.19k
        self.variables.insert(key, value.to_string().into());
216
3.19k
        self
217
3.19k
    }
218
219
409
    pub fn render_to_var(&mut self, path: &str, fragments: &[&str], key: &'variable str) -> Result<&mut Self, Error> {
220
409
        let str = self.template.render_internal(&self.variables, path, fragments)
?0
;
221
409
        self.variables.insert(key, str.into());
222
409
        Ok(self)
223
409
    }
224
225
1.49k
    pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> {
226
1.49k
        self.template.render_internal(&self.variables, path, fragments)
227
1.49k
    }
228
}